<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <script>
  // 定义Vue内部的实现
  function Vue(options) {
    // 将options中的data保存到Vue实例的_data上
    this._data = options.data

    // 实现数据代理
    this._proxy()

    // 初始对data对象中所有层次属性的数据劫持(实现响应式数据)
    this._defineReactive(this._data)

    // 初始化methods
    this._initMethods(options.methods)
  }

  Vue.prototype = {
    /* 
    对data中所有层次属性实现数据劫持(也就是定义响应式属性)
    */
    _defineReactive (data) {

      // 如果data不是对象, 直接结束
      if (data==null || typeof data != 'object') return

      Object.keys(data).forEach(key => {

        // 取出对应的属性值
        let value = data[key]
        
        // 通过递归调用, 实现对data内部对象的深度劫持
        this._defineReactive(value)


        // 给data中的key属性通过defineProperty添加getter和setter
        Object.defineProperty(data, key, {
          get () {
            console.log(`监视到读取data的${key}属性`)
            // return data[key]  // 会导致get执行, 死循环了
            return value
          },
          set (newValue) {
            // 保存最新的值
            value = newValue

            console.log(`监视到data的${key}属性变为了${newValue}`)
            console.log('准备去更新DOM')
          }
        })
      })
    },


    /* 
    将methods中所有的方法都添加到vm上
    将method中的this绑定为vm
    */
    _initMethods (methods) {
      // 遍历methods中每个method属性
      Object.keys(methods).forEach(key => {
        // this[key] = methods[key]  // 不能保证method中的this为vm
        this[key] = methods[key].bind(this) // 指定了this为vm
      })
    },

    // 实现数据代理的方法
    _proxy() {
      // 通过defineProperty给vue实例添加data中有所有属性
      // 遍历data中的所有属性, 分别去实现数据代理
      Object.keys(this._data).forEach(key => { // key就是msg/msg2
        // 给vue实例添加带getter和setter的属性
        Object.defineProperty(this, key, {
          // 在getter中: 读取并返回data对象中对应的属性值
          get () {
            console.log('getter')
            return this._data[key]
          },
          // 在setter中: 将最新的值保存到data对象的对应属性上
          set (value) {
            console.log('setter')
            this._data[key] = value
          }
        })
      })
    }
  }
  </script>

  <script>
    // 测试使用
    const vm = new Vue({
      data: {
        msg: 'abc',
        obj: {
          count: 1
        }
      },
      methods: {
        fn (){
          console.log('fn', this.msg)
        }
      }
    })

    console.log(vm)
    // vm.obj = {count: 2} 
    vm.obj.count = 3
    /* 
      读取vm.obj => vm上obj属性上的getter执行, 内部在读取data上obj属性 
        => data上的obj属性getter执行, 内部返回了data上obj属性值
      给obj对象的count属性设置为3 => count的setter调用, 内部就会去更新DOM
    */


    /* 
    vm.obj => getter 
    getter内部读取data.obj => 监视到读取data的obj属性
    obj.count = 3 => 监视到data的count属性变为了3
                      准备去更新DOM
    */

  </script>
</body>
</html>